package CppClassPrinter

import com.sdc.ast.controlflow.Assignment
import com.sdc.ast.controlflow.Invocation
import com.sdc.ast.controlflow.Return
import com.sdc.ast.controlflow.Statement
import com.sdc.ast.controlflow.Throw
import com.sdc.ast.expressions.BinaryExpression
import com.sdc.ast.expressions.Constant
import com.sdc.ast.expressions.Expression
import com.sdc.ast.expressions.New
import com.sdc.ast.expressions.UnaryExpression
import com.sdc.ast.expressions.identifiers.Field
import com.sdc.ast.expressions.identifiers.Variable
import com.sdc.cpp.CppClass
import com.sdc.cpp.CppClassField
import com.sdc.cpp.CppClassMethod
import pretty.*

fun printExpression(expression: Expression?, nestSize: Int): PrimeDoc =
        when (expression) {
            is Constant -> text(expression.getValue().toString())

            is BinaryExpression -> {
                val opPriority = expression.getPriority()

                val l = expression.getLeft()
                val left = when (l) {
                    is BinaryExpression ->
                        if (opPriority - l.getPriority() < 2)
                            printExpression(l, nestSize)
                        else
                            printExpressionWithBrackets(l, nestSize)
                    else -> printExpression(l, nestSize)
                }

                val r = expression.getRight()
                val right = when (r) {
                    is BinaryExpression ->
                        if (opPriority - r.getPriority() > 0 || opPriority == 3)
                            printExpressionWithBrackets(r, nestSize)
                        else
                            printExpression(r, nestSize)
                    else -> printExpression(r, nestSize)
                }

                group(left / (text(expression.getOperation()) + right))
            }

            is UnaryExpression -> {
                val operand = expression.getOperand()
                val expr = when (operand) {
                    is BinaryExpression ->
                        if (operand.getPriority() < 2)
                            printExpressionWithBrackets(operand, nestSize)
                        else
                            printExpression(operand, nestSize)
                    else -> printExpression(operand, nestSize)
                }
                text(expression.getOperation()) + expr
            }

            is Field -> text(expression.getName())
            is Variable -> text(expression.getName())

            is com.sdc.ast.expressions.Invocation -> {
                val funName = text(expression.getFunction() + "(")
                val args = expression.getArguments()!!.toArray()
                if (args.isEmpty())
                    funName + text(")")
                else {
                    var arguments = printExpression(args.get(0), nestSize)
                    for (arg in args.drop(1))
                        arguments = group(
                                (arguments + text(","))
                                / printExpression(arg as Expression, nestSize)
                        )
                    arguments = group(nest(nestSize, arguments))
                    group(funName + arguments + text(")"))
                }
            }

            is New -> group(
                    text("new") + nest(nestSize, line()
                    + printExpression(expression.getConstructor(), nestSize))
            )

            else -> throw IllegalArgumentException("Unknown Expression implementer!")
        }

fun printStatement(statement: Statement, nestSize: Int): PrimeDoc =
        when (statement) {
            is Invocation -> {
                val funName = text(statement.getFunction() + "(")
                val args = statement.getArguments()!!.toArray()
                if (args.isEmpty())
                    funName + text(")")
                else {
                    var arguments = printExpression(args.get(0), nestSize)
                    for (arg in args.drop(1))
                        arguments = group(
                                (arguments + text(","))
                                / printExpression(arg as Expression, nestSize)
                        )
                    arguments = group(nest(nestSize, arguments))
                    group(funName + arguments + text(")"))
                }
            }
            is Assignment -> group(
                    (printExpression(statement.getLeft(), nestSize) + text(" ="))
                    + nest(nestSize, line() + printExpression(statement.getRight(), nestSize))
            )
            is Return -> if (statement.getReturnValue() != null)
                group(
                        text("return") + nest(nestSize, line()
                        + printExpression(statement.getReturnValue(), nestSize))
                )
            else
                text("return")
            is Throw -> group(
                    text("throw") + nest(nestSize, line()
                    + printExpression(statement.getThrowObject(), nestSize))
            )

            else -> throw IllegalArgumentException("Unknown Statement implementer!")
        }

fun printStatements(statements: List<Statement>, nestSize: Int): PrimeDoc {
    if (statements.isEmpty())
        return nil()
    else {
        var body = printStatement(statements.get(0), nestSize) + text(";")
        for (statement in statements.drop(1)) {
            body = group(body / printStatement(statement, nestSize) + text(";"))
        }
        return body
    }
}

fun printExpressionWithBrackets(expression: Expression, nestSize: Int): PrimeDoc =
        text("(") + printExpression(expression, nestSize) + text(")")

fun printCppClass(cppClass: CppClass): PrimeDoc {
    var declaration = group(text("class ") + text(cppClass.getName()))

    val superClasses = cppClass.getSuperClasses()!!.toArray()
    if (!superClasses.isEmpty())
        declaration = group(declaration / text(": public " + superClasses.get(0)))
    val size = cppClass.getNestSize()
    for (superClass in superClasses.drop(1)) {
        declaration = group(
                (declaration + text(","))
                + nest(size, line() + text(superClass as String))
        )
    }

    var cppClassInnerCode = group(declaration)
    cppClassInnerCode = group(cppClassInnerCode + text("\n" + "{")) //todo redo : "/" doesn't work

    var publicCode = printMethodsFieldsDeclaration("public", cppClass.getPublicFields(), cppClass.getPublicMethods());
    var privateCode = printMethodsFieldsDeclaration("private", cppClass.getPrivateFields(), cppClass.getPrivateMethods());
    var protectedCode = printMethodsFieldsDeclaration("protected", cppClass.getProtectedFields(), cppClass.getPublicMethods());

    cppClassInnerCode = group(cppClassInnerCode / nest(size, publicCode))
    cppClassInnerCode = group(cppClassInnerCode / nest(size, privateCode))
    cppClassInnerCode = group(cppClassInnerCode / nest(size, protectedCode))

    var cppClassCode = group(cppClassInnerCode / text("};"))

    var cppOutMethods = printOutClassMethod(cppClass.getMethods()!!.get(0), cppClass.getName());
    for (classMethod in cppClass.getMethods()!!.drop(1))
        cppOutMethods =
        cppOutMethods /
        nest(size, printOutClassMethod(classMethod, cppClass.getName()))
    cppClassCode = group(cppClassCode / cppOutMethods)
    return cppClassCode
}

fun printClassMethodHead(classMethod: CppClassMethod): PrimeDoc {
    val declaration = text(classMethod.getReturnType() + classMethod.getName() + "(");
    if (classMethod.getLastLocalVariableIndex() == 0)
        return (declaration + text(");"))
    else {
        val variables = classMethod.getParameters()!!.toArray()
        var arguments = group(text(variables.get(0)))
        for (variable in variables.drop(1)) {
            arguments = group(
                    (arguments + text(","))
                    + nest(2 * classMethod.getNestSize(), line() + text(variable as String))
            )
        }
        return (declaration + arguments + text(");"))
    }
}
fun printClassMethod(classMethod: CppClassMethod): PrimeDoc {
    val declaration = text(classMethod.getModifier() + classMethod.getReturnType() + classMethod.getName() + "(");
    if (classMethod.getLastLocalVariableIndex() == 0)
        return (declaration + text(") {")) +
        nest(
                classMethod.getNestSize(),
                line() + printStatements(classMethod.getBody(), classMethod.getNestSize())
        ) / text("}")
    else {
        val variables = classMethod.getParameters()!!.toArray()
        var arguments = group(text(variables.get(0)))
        for (variable in variables.drop(1)) {
            arguments = group(
                    (arguments + text(","))
                    + nest(2 * classMethod.getNestSize(), line() + text(variable as String))
            )
        }
        return (declaration + arguments + text(") {")) +
        nest(
                classMethod.getNestSize(),
                line() + printStatements(classMethod.getBody(), classMethod.getNestSize())
        ) / text("}")
    }
}

fun printOutClassMethod(classMethod: CppClassMethod, className: String): PrimeDoc {
    val declaration = text(classMethod.getReturnType() + className + "::" + classMethod.getName() + "(");
    if (classMethod.getLastLocalVariableIndex() == 0)
        return (declaration + text(") {")) +
        nest(
                classMethod.getNestSize(),
                line() + printStatements(classMethod.getBody(), classMethod.getNestSize())
        ) / text("}")
    else {
        val variables = classMethod.getParameters()!!.toArray()
        var arguments = group(text(variables.get(0)))
        for (variable in variables.drop(1)) {
            arguments = group(
                    (arguments + text(","))
                    + nest(2 * classMethod.getNestSize(), line() + text(variable as String))
            )
        }
        return (declaration + arguments + text(") {")) +
        nest(
                classMethod.getNestSize(),
                line() + printStatements(classMethod.getBody(), classMethod.getNestSize())
        ) / text("}")
    }
}

fun printClassField(classField: CppClassField): PrimeDoc =
        text(classField.getModifier() + classField.getType() + classField.getName() + ";")

fun printClassFieldHead(classField: CppClassField): PrimeDoc =
        text(classField.getType() + classField.getName() + ";")

fun printMethodsFieldsDeclaration(modifier: String, fields: List<CppClassField>, methods: List<CppClassMethod>): PrimeDoc {
    if(fields.isEmpty() && methods.isEmpty()) return nil();
    var  declaration = text(modifier + ":")
    if(fields.isEmpty()){
        var body = printClassMethodHead(methods.get(0))
        for (method in methods.drop(1)){
            body = body / printClassMethodHead(method)
        }
        return declaration / body
    } else{
        var body = printClassFieldHead(fields.get(0))
        for (field in fields.drop(1)) {
            body = body / printClassFieldHead(field)
        }
        for (method in methods){
            body = body / printClassMethodHead(method)
        }
        return declaration / body
    }
}

